﻿using System;
using System.Collections.Generic;
using metroioc.Container;

namespace MetroIoc
{
    public class Singleton : IRegistration
    {
        class Registration : Tuple<bool, Func<LifetimeScope, object>, object>
        {
            public Registration(bool isResolved, Func<LifetimeScope, object> resolverFunc, object resolvedInstance)
                : base(isResolved, resolverFunc, resolvedInstance) { }

            public bool IsResolved { get { return this.Item1; } }
            public Func<LifetimeScope, object> ResolverFunc { get { return this.Item2; } }
            public object ResolvedInstance { get { return this.Item3; } }
        }

        private class CompositeKey : Tuple<Type, string>
        {
            public CompositeKey(Type type, string key)
                : base(type, key) { }
        }

        private static readonly IList<Type> RegisteredTypes = new List<Type>();
        private readonly static IDictionary<CompositeKey, Registration> Singletons
            = new Dictionary<CompositeKey, Registration>();

        public void Register(Type type, string key, Func<LifetimeScope,object> resolverFunc)
        {
            if (resolverFunc == null)
                throw new ArgumentNullException("resolverFunc");

            var compositeKey = new CompositeKey(type, key);
            Singletons[compositeKey] = new Registration(false, resolverFunc, null);
        }

        internal void Register(Type type, string key, object instance)
        {
            // Add the type to the list if it doesn't already exist
            if (!RegisteredTypes.Contains(type))
                RegisteredTypes.Add(type);

            //  Add/edit the current singleton instance
            var compositeKey = new CompositeKey(type, key);
            Singletons[compositeKey] = new Registration(true, null, instance);
        }

        public object Resolve(Type type, string key, Func<LifetimeScope, object> resolverFunc, LifetimeScope resolver)
        {
            object result;

            var compositeKey = new CompositeKey(type, key);
            if (Singletons.ContainsKey(compositeKey))
            {
                var registration = Singletons[compositeKey];
                if (registration.IsResolved)
                    return registration.ResolvedInstance;

                result = registration.ResolverFunc(resolver);
                Singletons[compositeKey] = new Registration(true, null, result);
                return result;
            }

            if (resolverFunc == null)
                throw new ArgumentNullException("resolverFunc");

            result = resolverFunc(resolver);
            Singletons[compositeKey] = new Registration(true, null, result);

            return result;
        }
    }
}
